#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include <drv/codec.h>
#include <ck_codec.h>
#include <soc.h>
#include <yun_pmu.h>
#include <drv/pmu.h>
#include <drv/rtc.h>
#include <pin_name.h>
#include <drv/gpio.h>
#include <pinmux.h>
#include "app_main.h"

#define MIC_RATE (16000)
#define MIC_SAMPLE_BIT (16)
#define MIC_TIME (80)
#define MIC_RECV_SIZE (MIC_RATE / 1000 * MIC_SAMPLE_BIT / 8 *MIC_TIME)
#define MIC_PERIOD_SIZE (MIC_RATE / 1000 * MIC_SAMPLE_BIT / 8 *10)
#define MIC_SAVE_BUFFER_SIZE (MIC_PERIOD_SIZE)

#define PREG32(addr) *((volatile unsigned int *)addr)
#define writel(b,addr) (void)((*(volatile unsigned int *) (addr)) = (b))

typedef struct {
    codec_input_t    codec;
    int              recv_size;
    int              ticks;
    uint8_t         *mic;
    volatile int     vad_flag;
} app_lpm_t;

typedef struct {
    int vad_th;
    int policy;
    int standby_cb;
} app_param_t;

extern volatile int g_tick_cnt;
app_lpm_t g_app_lpm;
uint8_t g_mic_buffer[MIC_SAVE_BUFFER_SIZE];
uint8_t g_codec_buffer[MIC_RECV_SIZE];
app_param_t *g_app_param = (app_param_t *)-1;

#if (defined CONFIG_SW_VAD) && CONFIG_SW_VAD
volatile int g_recv_flag = 0;
int wake_times = 0;

static void record_start(codec_input_t *codec)
{
    csi_codec_vad_enable(codec, 0);
    csi_codec_input_start(codec);
}

static void record_stop(codec_input_t *codec)
{
    csi_codec_input_stop(codec);
    csi_codec_input_close(codec);
}

static int sw_vad(void *data, int len)
{
    int16_t *buff = (int16_t *)(data);

    for (int i = 0; i < len / 2; i++) {
        if (buff[i] >= 500) {
            wake_times ++;
            printf("wake times ++ [%d]\n", wake_times);
            break;
        }
    }

    if (wake_times > 15 && wake_times < 30) {
        return 1;
    }

    return 0;
}
#endif

static void input_event_cb(int idx, codec_event_t event, void *arg)
{
    if (event == CODEC_EVENT_PERIOD_READ_COMPLETE) {
#if (defined CONFIG_SW_VAD) && CONFIG_SW_VAD
        g_recv_flag = 1;
#else
        csi_codec_input_read(&g_app_lpm.codec, &g_mic_buffer[0], MIC_SAVE_BUFFER_SIZE);
#endif
    } else if (event == CODEC_EVENT_VAD_TRIGGER) {
        g_app_lpm.vad_flag = 1;
    } else {
    }
}

static int codec_init(codec_input_t *codec)
{
    csi_codec_init(0);

    codec->buf              = g_codec_buffer;
    codec->buf_size         = MIC_RECV_SIZE;
    codec->cb               = input_event_cb;
    codec->cb_arg           = &g_app_lpm;
    codec->ch_idx           = 0;
    codec->codec_idx        = 0;
    codec->period           = MIC_PERIOD_SIZE;
    codec->vad_threshold    = g_app_param->vad_th;

    int ret = csi_codec_input_open(codec);

    if (ret != 0) {
        return -1;
    }

    // printf("1111111111(%d)\n", codec->period);
    codec_input_config_t config;
    config.bit_width    = MIC_SAMPLE_BIT;
    config.sample_rate  = MIC_RATE;
    config.channel_num  = 1;
    ret = csi_codec_input_config(codec, &config);

    if (ret != 0) {
        return -1;
    }

    csi_codec_input_set_analog_gain(codec, 6);

    // csi_codec_input_start(codec);

    csi_codec_vad_enable(codec, 1);

    // csi_vic_set_wakeup_irq(CODEC_WHISPER_IRQn);

    csi_codec_input_path_en(codec, 1);
    return 0;
}

static int codec_deinit(codec_input_t *codec)
{
    // int ret =  csi_codec_input_read(codec, &g_mic_buffer[g_app_lpm.pos], MIC_SAVE_BUFFER_SIZE-g_app_lpm.pos);

    // g_app_lpm.pos += ret;

    csi_codec_uninit(0);

    return 0;
}

static void gpio_config(void)
{
    drv_pinmux_config(PA25, PIN_FUNC_GPIO);
    gpio_pin_handle_t pin = NULL;
    pin = csi_gpio_pin_initialize(PA25, NULL);

    csi_gpio_pin_config_mode(pin, GPIO_MODE_PULLNONE);
    csi_gpio_pin_config_direction(pin, GPIO_DIRECTION_INPUT);
    csi_gpio_pin_set_irq(pin, GPIO_IRQ_MODE_LOW_LEVEL, 1);
}

static void pmu_codec_vad_config(void)
{
    while ((*(volatile uint32_t *)(0x30000208)) != 0);

    //PREG32(0xE000E140) = 0x40; //唤醒源

    #define PMU_WKUPMASK 0x30000018
    #define PMU_LPCR     0x30000014
    #define PMU_DLC_IFR  0x30000210
    PREG32(PMU_WKUPMASK) = 0xF;
    // config PMU lowpower
    PREG32(PMU_LPCR) = 0x1;
    //config CPU clear pmu intr
    PREG32(PMU_DLC_IFR) = 0x1f;
    while ((*(volatile uint32_t *)(PMU_DLC_IFR)) != 0);

}

static void pmu_gpio_config(void)
{
    while ((*(volatile uint32_t *)(0x30000208)) != 0);

    //PREG32(0xE000E140) = 0x40; //唤醒源

    #define PMU_WKUPMASK 0x30000018
    #define PMU_LPCR     0x30000014
    #define PMU_DLC_IFR  0x30000210

    PREG32(PMU_WKUPMASK) = 0xF;
    // config PMU lowpower
    PREG32(PMU_LPCR) = 0x1;
    //config CPU clear pmu intr
    PREG32(PMU_DLC_IFR) = 0x1f;
    while (PREG32(PMU_DLC_IFR) != 0);

    PREG32(0xe000e280) = 0x10040;
    PREG32(0xe000e284) = 0x400;
    PREG32(0xE000E140) = 0x40;
    PREG32(0xE000E144) = 0x400;
}

static int lpm_enter(int policy)
{
    printf("enter lpm(%d)\n\n\n", policy);
#if (defined CONFIG_SW_VAD) && CONFIG_SW_VAD
    if(1 == policy) {
        return 0;
    }
#endif

    PREG32(0x30000010) |= (1 << 19);         //pll 小数分频
    PREG32(0x30000014) |= 0x2;  // 0.9v

    if (policy == 1) {
        pmu_codec_vad_config();
        csi_codec_vad_enable(&g_app_lpm.codec, 1);
    } else {
        pmu_gpio_config();
    }

    if (policy == 1) {
        asm("wait\n\n");
    } else {
        asm("stop\n\n");
    }

    PREG32(0x30000014) = 0x1;
    PREG32(0x30000010) &= (~(1 << 19));         //pll 小数分频

    if (policy == 1) {
        csi_codec_vad_enable(&g_app_lpm.codec, 0);
    }

    return 0;
}

static void sdram_auto_flash_enable(int flag)
{
    if (flag) {
        *(volatile uint32_t *)(0x1a00000c) = 0x3002;
        while((*(volatile uint32_t *)(0x1a00000c) & 0x800) != 0x800);
    } else {
        *(volatile uint32_t *)(0x1a00000c) = 0x3009;
        while((*(volatile uint32_t *)(0x1a00000c) & 0x800)  != 0);
        // printf("SDRAM into Auto Refresh, SDRAM OK\n");
    }
}

void lpm_init(void)
{
    g_app_lpm.mic = g_mic_buffer;
    codec_input_t *codec = &g_app_lpm.codec;
    int policy = g_app_param->policy;

    rtc_handle_t rtc_hdl = csi_rtc_initialize(0, NULL);
    csi_rtc_start(rtc_hdl);
    csi_rtc_enable_alarm(rtc_hdl, 1);
    printf("lpm start\n");

    if (g_app_param->policy == 1) {
        codec_init(codec); //do not set sdram auto flash before codec_init()
    } else {
        gpio_config();
        PREG32(0x30000008) = 0;         //must close cpu1&2
        PREG32(0x30000068) = g_app_param->standby_cb;
    }

    // yun_sdram_auto_flash_enable(1);
    sdram_auto_flash_enable(1);
    lpm_enter(policy);

#if (defined CONFIG_SW_VAD) && CONFIG_SW_VAD
    printf("enter sw vad flow\n");
    drv_set_cpu_freq(0, 52500000);
    app_uart_init();
    record_start(codec);
    while (1) {
        if (g_recv_flag) {
            int ret = csi_codec_input_read(&g_app_lpm.codec, g_mic_buffer, MIC_SAVE_BUFFER_SIZE);
            g_app_lpm.recv_size = ret;
            if (ret) {
                ret = sw_vad(g_mic_buffer, ret);
                if (ret > 0) {
                    break;
                }
            }
            g_recv_flag = 0;
        }
    }
    record_stop(codec);
    drv_set_cpu_freq(0, 210000000);
#endif
    // yun_sdram_auto_flash_enable(0);
    sdram_auto_flash_enable(0);

    codec_deinit(codec);
    g_app_lpm.ticks = g_tick_cnt;
}
